package com.gatesim;


import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Timer;
import java.util.Vector;

import com.gatesim.GateWorld.GWTT;

import android.app.Activity;
import android.content.res.Resources;
import android.gesture.Gesture;
import android.gesture.GestureLibraries;
import android.gesture.GestureLibrary;
import android.gesture.GestureOverlayView;
import android.gesture.GestureOverlayView.OnGesturePerformedListener;
import android.gesture.GestureStroke;
import android.gesture.Prediction;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

public class GateSimActivity extends Activity implements OnGesturePerformedListener{
	
	private GestureLibrary gLib;
	private RelativeLayout viewHolder;
	private RelativeLayout tutViewHolder;
	public static GestureOverlayView gestures;
	public GateWorld world;
	public Button simulate;
	public Button clear;
	public Button end;
	public Button animate;
	public boolean onTutorial;
	public Button tutorial;
	public Button tutExit;
	public int buttonIds = 1;
	public ImageView tutorialView;

	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        
        //Setting all of the views
        viewHolder = new RelativeLayout(this);
        tutViewHolder = new RelativeLayout(this);
       
        //Prep for tutorial view
        tutorialView = new ImageView(this);
        tutorialView.setId(buttonIds++);
        tutorialView.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.tutorial));
        tutViewHolder.addView(tutorialView);
        RelativeLayout.LayoutParams tutExitLayout = tutExitButtonInit();
        tutViewHolder.addView(tutExit,tutExitLayout);
        
        //The Gesture Canvas
        world = new GateWorld(this);
        viewHolder.addView(world, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 
															ViewGroup.LayoutParams.MATCH_PARENT));
        
        tutorialButtonInit();
        onTutorial = false;
        
        endButtonInit();
        
        animateButtonInit();
        
        simulateButtonInit();
        
        clearButtonInit();
        
        //The Gesture Library
        gLib = GestureLibraries.fromRawResource(this, R.raw.gestures);
        if(!gLib.load()){
        	Log.w("com.gatesim.raw","can't load gesture libary");
        }
        
        gestures = new GestureOverlayView(this);
        gestures.addView(viewHolder); 
		gestures.addOnGesturePerformedListener(this);
		gestures.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);
		gestures.setOrientation( GestureOverlayView.ORIENTATION_HORIZONTAL );
		
        gestures.setEventsInterceptionEnabled(false);
		setContentView(gestures);
        GateSimActivity.gestures.setEventsInterceptionEnabled(false);
        
        
        /*** SCHEDULER ***/
        world._gwTimer = new Timer();
        GateWorld.GWTT gwtt = world.new GWTT(world);
            // TimerTask, offset, period.
        world._gwTimer.schedule(gwtt, 0, 1000);
    }

	private RelativeLayout.LayoutParams tutExitButtonInit() {
		tutExit = new Button(this);
        tutExit.setText("Exit");
        tutExit.setId(buttonIds++);
        RelativeLayout.LayoutParams tutExitLayout = 
        		new RelativeLayout.LayoutParams(
        						RelativeLayout.LayoutParams.WRAP_CONTENT,
        						RelativeLayout.LayoutParams.WRAP_CONTENT);
        tutExitLayout.addRule(RelativeLayout.BELOW, tutorialView.getId());
        tutExitLayout.addRule(RelativeLayout.CENTER_IN_PARENT);        
        
        //The Simulate Button: Defines its functionality
        tutExit.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
            	viewHolder.removeView(tutViewHolder);
            	onTutorial = false;
            }
        });
		return tutExitLayout;
	}

	private void clearButtonInit() {
		//The Clear Button
        clear = new Button(this);
        clear.setText("Clear");
        clear.setId(buttonIds++);
        RelativeLayout.LayoutParams clearLayout = 
        		new RelativeLayout.LayoutParams(
        						RelativeLayout.LayoutParams.WRAP_CONTENT,
        						RelativeLayout.LayoutParams.WRAP_CONTENT);
        clearLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        clearLayout.addRule(RelativeLayout.LEFT_OF, simulate.getId());
        viewHolder.addView(clear,clearLayout);
        
        //The Simulate Button: Defines its functionality
        clear.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
            	world.clearWorkspace();
            	Toast.makeText(GateSimActivity.this, "Goodbye Gates!", Toast.LENGTH_SHORT).show();
            }
        });
	}

	private void endButtonInit() {
		//The End Simulation Button
        end = new Button(this);
        end.setText("End Sim/Anim");
        end.setId(buttonIds++);
        RelativeLayout.LayoutParams endLayout = 
        		new RelativeLayout.LayoutParams(
        						RelativeLayout.LayoutParams.WRAP_CONTENT,
        						RelativeLayout.LayoutParams.WRAP_CONTENT);
        endLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP); 
        endLayout.addRule(RelativeLayout.LEFT_OF, tutorial.getId());
        viewHolder.addView(end,endLayout);
        
        //The End Button: Defines its functionality
        end.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
            	world.endSim();
            	GateWorld.endAnimateMode();
            	Toast.makeText(GateSimActivity.this, "Ending!", Toast.LENGTH_SHORT).show();
            }
        });
	}

	private void simulateButtonInit() {
		//The Simulate Button
        simulate = new Button(this);
        simulate.setText("Simulate");
        simulate.setId(buttonIds++);
        RelativeLayout.LayoutParams simulateLayout = 
        		new RelativeLayout.LayoutParams(
        						RelativeLayout.LayoutParams.WRAP_CONTENT,
        						RelativeLayout.LayoutParams.WRAP_CONTENT);
        simulateLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP); 
        simulateLayout.addRule(RelativeLayout.LEFT_OF, animate.getId()); 
        viewHolder.addView(simulate,simulateLayout);
        
        //The Simulate Button: Defines its functionality
        simulate.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                GateWorld.simulationMode = true;
            	world.simulate();
            	Toast.makeText(GateSimActivity.this, "Simulate!", Toast.LENGTH_SHORT).show();
            }
        });
	}

	private void animateButtonInit() {
		//The Animate Button
        animate = new Button(this);
        animate.setText("Animate");
        animate.setId(buttonIds++);
        RelativeLayout.LayoutParams animateLayout = 
        		new RelativeLayout.LayoutParams(
        						RelativeLayout.LayoutParams.WRAP_CONTENT,
        						RelativeLayout.LayoutParams.WRAP_CONTENT);
        animateLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP); 
        animateLayout.addRule(RelativeLayout.LEFT_OF, end.getId());
        viewHolder.addView(animate,animateLayout);
        
        //The End Button: Defines its functionality
        animate.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
            	GateWorld.animateMode();
                GateWorld.simulationMode = true;
            	world.simulate();
            	Toast.makeText(GateSimActivity.this, "Animate!", Toast.LENGTH_SHORT).show();
            }
        });
	}

	private void tutorialButtonInit() {
		//The Tutorial Button
        tutorial = new Button(this);
        tutorial.setText("Tutorial");
        tutorial.setId(buttonIds++);
        RelativeLayout.LayoutParams tutorialLayout = 
        		new RelativeLayout.LayoutParams(
        						RelativeLayout.LayoutParams.WRAP_CONTENT,
        						RelativeLayout.LayoutParams.WRAP_CONTENT);
        tutorialLayout.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
        tutorialLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); 
        viewHolder.addView(tutorial,tutorialLayout);
        
        //The Tutorial Button: Displays the Cheat Sheet
        tutorial.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
            	if(!onTutorial) {
            		onTutorial = true;
	            	RelativeLayout.LayoutParams tutorialViewLayout = 
	                		new RelativeLayout.LayoutParams(
	                						RelativeLayout.LayoutParams.WRAP_CONTENT,
	                						RelativeLayout.LayoutParams.WRAP_CONTENT);
	            	tutorialViewLayout.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
	            	tutorialViewLayout.addRule(RelativeLayout.CENTER_VERTICAL, RelativeLayout.TRUE);
	            	viewHolder.addView(tutViewHolder,tutorialViewLayout);
	            	Toast.makeText(GateSimActivity.this, "Let's Learn!", Toast.LENGTH_SHORT).show();
            	}
            }
        });
	}
    
    private boolean checkBounds(Gate newGate){
    	if(!(world.gatesInUse.isEmpty())){
	        for(Gate g : world.gatesInUse){
	            if( (Math.abs((newGate.getCenter().x - g.getCenter().x)) <  newGate.getWidth()) &&
	                 (Math.abs((newGate.getCenter().y - g.getCenter().y)) < newGate.getHeight()) ){
	            	//if g is 0 and newGate is 1
	            	if(newGate.isVolt() && g.isVolt()){
	            		if( ((Volt)g).getCharge() != ((Volt)newGate).getCharge() ){
	            			((Volt)g).setCharge(((Volt)newGate).getCharge());
	            		}
	            	}
	                return false;
	           }
	        }
    	}
        return true;
    }
    
    private Gate checkBoundsForRemove(RectF rf){
        if(!(world.gatesInUse.isEmpty())){
    	        for(Gate g : world.gatesInUse){
    	        	if ( (Math.abs((rf.centerX() - g.getCenter().x)) <  rf.width()) &&
    	                     (Math.abs((rf.centerY() - g.getCenter().y)) <  rf.height()) ){
    	            	return g;
    	           }
        	}
        }
        return null;
    }
    
    
    private float dotProduct( PointF A, PointF B ){
        float retFloat = (A.x * B.x) + (A.y * B.y);
        return retFloat;
    }
    
    private ArrayList<Wire> checkIntersect( PointF beg, PointF end ){
        ArrayList <Wire> intersectingWires = new ArrayList<Wire>();
        if(!(world.wiresToDraw.isEmpty())){
            for ( Wire w : world.wiresToDraw ){ //int j = 0; j < world.wiresToDraw.size(); j++ ){
//                Wire w = world.wiresToDraw.get(j);
                //for any two adjacent points on wire, look for the intersection.
                for (int i=0; i < w.pointVector.size()-1; i++){
                    Point p1 = w.pointVector.get(i);   //p1
                    Point p2 = w.pointVector.get(i+1); //p2

                    float a1 = p2.x - p1.x;
                    float b1 = p2.y - p1.y;
                    
                    //Wire vector
                    PointF E = new PointF( a1, b1); 
                    //Perpendicular wire vector
                    PointF P = new PointF (-E.y, E.x);
                    
                    //Later: move up so we dont do everytime
                    float a2 = end.x - beg.x;
                    float b2 = end.y - beg.y;
                    
                    PointF F = new PointF ( a2, b2); 
                    PointF Q = new PointF (-F.y, F.x);
                    
                    if ( dotProduct(F, P) == 0 ){
                        
                    	//parrallel/colinear
                        if ( dotProduct( new PointF(p1.x - beg.x, p1.y - beg.y), P ) == 0 ){
                            Log.d("IntersectCheck", "Colinear");
                            // colinear?
                            intersectingWires.add( w );//new Integer(j));
                            break;
                        }else{
                            Log.d("IntersectCheck", "Parrallel");
                        }

                    }
                    else{
                        // g is the percentage distance that AB WOULD intersect CD at some point.
                        float g = dotProduct( new PointF(p1.x - beg.x, p1.y - beg.y), P ) / dotProduct(F, P);
                        // h is the percentage distance that CD WOULD intersect AB at some point.
                        float h = dotProduct( new PointF(beg.x - p1.x, beg.y - p1.y), Q ) / dotProduct(E, Q);
                        if ( (h > 0 && h < 1) && (g > 0 && g < 1) ){
                            Log.d("IntersectCheck", "Will Intersect: h: " + h + " g: " + g);
                            // intersection
                            intersectingWires.add( w );
                            break;
                        }else{
                            Log.d("IntersectCheck", "Could Intersect:  h: " + h + " g: " + g);
                        }
                    }
                }
            }
        }
        return intersectingWires;
    }
    
    public void handlePinch(Gesture gesture){
//        Toast.makeText( this , "Handling Pinch. ",  Toast.LENGTH_SHORT).show();
        PointF newBeg = null;
        for ( GestureStroke gs : gesture.getStrokes() ){
            PointF pBeg = new PointF ( gs.points[0], gs.points[1] );
            PointF pEnd = new PointF ( gs.points[gs.points.length - 2], gs.points[gs.points.length - 1] );
            if (newBeg != null){
                Toast.makeText( this , "Pinch Point",  Toast.LENGTH_SHORT).show();
                ArrayList<Wire> cI0 = checkIntersect(newBeg, pBeg);
                remIntersectionHelper(cI0);
            }
            newBeg = pEnd;
            
            ArrayList<Wire> cI = checkIntersect(pBeg, pEnd);
            remIntersectionHelper(cI);

            Log.d("Pinch", "Evaluating a PinchStroke: Point beg: (" + pBeg.x + ", " + pBeg.y + ")" +                                                     "Point end: (" + pEnd.x + ", " + pEnd.y + ")");
        }
    }
    
    public void remIntersectionHelper( ArrayList<Wire> cI ){
        for ( Wire w : cI ){
            if( w != null){
                if (w.getDest() != null)  
                    w.getDest().unoccupy();
                if (w.getSource() != null) 
                    w.getSource().unoccupy();
                world.wiresToDraw.remove( w );
                
                if ( w.getGateDest() != null )
                w.getGateDest().disconnectWire(w);
                
                if ( w.getGateSource() != null )
                w.getGateSource().disconnectWire(w);
                
                Log.d("WireCount", "There are now: " + world.wiresToDraw.size() + " Wires.");
            }
        }
    }
    
//  @Override
	public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture){
        // Toast.makeText( this , "Gesture made of: " + gesture.getStrokesCount() + " stroke.",
        //        Toast.LENGTH_SHORT).show();
		if(!world.getIsConnecting() && !world.getIsMoving()){
			ArrayList<Prediction> predictions = gLib.recognize(gesture);
			
			double TotalPredictionScore = 0;
			//int numberOfMatches = 0;
			double andScore = 0, orScore = 0, oneScore = 0, zeroScore = 0, notScore = 0, xorScore = 0; 
			for(Prediction prediction : predictions){
				if ( prediction.score > 1.0 ){
					if(gesture.getStrokesCount() == 1){
						Vector<String> strVec = new Vector<String>();
						strVec.add("and"); strVec.add("and1"); strVec.add("and2"); strVec.add("and3");
						String strArr [] = new String[0];
						for ( String s : strVec.toArray( strArr ) ){
						    if ( s.equals( prediction.name ) ){
						        andScore += prediction.score / strVec.size();
						        TotalPredictionScore += prediction.score / strVec.size();
						        break;
						    }
						}
						strVec = new Vector<String>();
                        strVec.add("or"); strVec.add("or1"); strVec.add("or2"); strVec.add("or3");
                        for ( String s : strVec.toArray( strArr ) ){
                            if ( s.equals( prediction.name ) ){
                                orScore += prediction.score / strVec.size();
                                TotalPredictionScore += prediction.score / strVec.size();
                                break;
                            }
                        }
                        
                        strVec = new Vector<String>();
                        strVec.add("xor"); strVec.add("xor1"); strVec.add("xor2"); strVec.add("xor3");
                        for ( String s : strVec.toArray( strArr ) ){
                            if ( s.equals( prediction.name ) ){
                                xorScore += prediction.score / strVec.size();
                                TotalPredictionScore += prediction.score / strVec.size();
                                break;
                            }
                        }
                        
                        strVec = new Vector<String>();
                        strVec.add("not"); strVec.add("not1"); strVec.add("not2"); strVec.add("not3");
                        for ( String s : strVec.toArray( strArr ) ){
                            if ( s.equals( prediction.name ) ){
                                notScore += prediction.score / strVec.size();
                                TotalPredictionScore += prediction.score / strVec.size();
                                break;
                            }
                        }
                        
                        strVec = new Vector<String>();
                        strVec.add("one"); strVec.add("one1"); strVec.add("one2"); strVec.add("one3");
                        for ( String s : strVec.toArray( strArr ) ){
                            if ( s.equals( prediction.name ) ){
                                oneScore += prediction.score / strVec.size();
                                TotalPredictionScore += prediction.score / strVec.size();
                                break;
                            }
                        }
                        
                        strVec = new Vector<String>();
                        strVec.add("zero"); strVec.add("zero1"); strVec.add("zero2"); strVec.add("zero3");
                        for ( String s : strVec.toArray( strArr ) ){
                            if ( s.equals( prediction.name ) ){
                                zeroScore += prediction.score / strVec.size();
                                TotalPredictionScore += prediction.score / strVec.size();
                                break;
                            }
                        }
                       /*if("and".equals(prediction.name)){
                            addAndGate(gesture);
                            break;
                        }
	                    if("or".equals(prediction.name)){ 
	                        addOrGate(gesture);
	                        break;
	                    }
	                    if("one".equals(prediction.name)){ 
	                        addOneVolt(gesture);
	                        break;
	                    }
	                    if("zero".equals(prediction.name)){
	                        addZeroVolt(gesture);
	                        break;
	                    }*/
					}else if(gesture.getStrokesCount() == 2){
	                    if(prediction.name.equals("delete")){
	                        removeGate(gesture);
	                        break;
	                    }
					}
				}
			}// End of For Loop
			if(gesture.getStrokesCount() == 1){
    			double dArr[] = { andScore, orScore, oneScore, zeroScore, xorScore, notScore };
    			double max = 0;
    			int index = -1;
    			for (int i = 0; i < dArr.length; i++){
    			    double d = dArr[i];
    			    if ( max < d ){
    			        max = d;
    			        index = i;
    			    }
    			}
//                Toast.makeText( this , "Gesture certainty: " + max / TotalPredictionScore,
//                        Toast.LENGTH_SHORT).show();
    			switch (index){
    			    case 0: addAndGate(gesture); break;
    			    case 1: addOrGate(gesture); break;
    			    case 2: addOneVolt(gesture); break;
    			    case 3: addZeroVolt(gesture); break;
    	            case 4: addXorGate(gesture); break;
                    case 5: addNotGate(gesture); break;
    			    case -1:
    			    default: break;
    			}
			}
		}
	}

	//
	// The four methods below check if a gate is in bounds. If it is,
	// the gate is created an added to the gatesInUse.
	//
    private void removeGate(Gesture gesture){
        RectF gbound = gesture.getBoundingBox();
        Gate toBeRemoved = checkBoundsForRemove(gbound);
        
        if(toBeRemoved != null){
        	//remove wires associated with gate
            Wire w;
            ArrayList<Wire> wiresToRemove = new ArrayList<Wire>();
            for(int i = 0; i<world.wiresToDraw.size(); i++){
            	w = world.wiresToDraw.get(i);
            	if(w.getGateDest() == toBeRemoved || w.getGateSource() == toBeRemoved){
            		wiresToRemove.add(w);
                	//loop through the gates and break all connections & resolve charges
            		for(Gate g : world.gatesInUse) {
            			g.disconnectWire(w);
            		}
            	}
            }
            world.wiresToDraw.removeAll(wiresToRemove);
        	world.gatesInUse.remove(toBeRemoved);
        }
    }
	private void addZeroVolt (Gesture gesture){
		RectF gbound = gesture.getBoundingBox();
		Volt volt = new Volt(new Point( (int)gbound.left + ((int)gbound.width()  / 2) ,
		                                (int)gbound.top  + ((int)gbound.height() / 2)),
		                                0 );
		boolean clear = checkBounds(volt);
		if (clear) 
			world.gatesInUse.add(volt);
	}

	private void addOneVolt (Gesture gesture){
		RectF gbound = gesture.getBoundingBox();
		Volt volt = new Volt (new Point( (int)gbound.left + ((int)gbound.width()  / 2) ,
		                               (int)gbound.top  + ((int)gbound.height() / 2)),
		                               1 );
		boolean clear = checkBounds(volt);
		if (clear) 
			world.gatesInUse.add(volt);
	}

	private void addOrGate (Gesture gesture) {
		RectF gbound = gesture.getBoundingBox();
		OrGate or = new OrGate(new Point( (int)gbound.left + ((int)gbound.width()  / 2) ,
		                                  (int)gbound.top  + ((int)gbound.height() / 2) ));
		boolean clear = checkBounds(or);
		if (clear)
			world.gatesInUse.add(or);
	}
	
   private void addXorGate (Gesture gesture) {
        RectF gbound = gesture.getBoundingBox();
        XorGate xor = new XorGate(new Point( (int)gbound.left + ((int)gbound.width()  / 2) ,
                                             (int)gbound.top  + ((int)gbound.height() / 2) ));
        boolean clear = checkBounds(xor);
        if (clear)
            world.gatesInUse.add(xor);
    }
   
    private void addNotGate (Gesture gesture) {
        RectF gbound = gesture.getBoundingBox();
        NotGate not = new NotGate(new Point( (int)gbound.left + ((int)gbound.width()  / 2) ,
                                             (int)gbound.top  + ((int)gbound.height() / 2) ));
        boolean clear = checkBounds(not);
        if (clear)
            world.gatesInUse.add(not);
    }

	private void addAndGate(Gesture gesture) {
		RectF gbound = gesture.getBoundingBox();
		AndGate and = new AndGate(new Point( (int)gbound.left + ((int)gbound.width()  / 2) ,
		                                     (int)gbound.top  + ((int)gbound.height() / 2) ));
		boolean clear = checkBounds(and); 
		if (clear)
		    world.gatesInUse.add(and);
	}
	
}